package de.tuhh.simsearch.model.persistence.contentmgmt.mltools;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeTypes;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.SAXException;

import de.tuhh.simsearch.constants.Constants;
import de.tuhh.simsearch.model.persistence.contentmgmt.entities.CategoryEntity;
import de.tuhh.simsearch.model.persistence.contentmgmt.entities.FileEntity;
import de.tuhh.simsearch.model.persistence.contentmgmt.mltools.interfaces.IndexerInterface;

/**
 * The LuceneIndexer implements the functions specified by the {@link IndexerInterface}.
 * The LuceneIndexer uses the Lucene API and the Tika API to index files.
 * The Tika API is used to parse content of files. 
 * 
 * @author SimSearch 1
 *
 */
public class LuceneIndexer implements IndexerInterface {

  private final File INDEX_PATH = new File("index.dir");
  private final int PARSE_SIZE = 10 * 1024 * 1024;

  private Tika tika = new Tika();
  private StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_46);


  /**
   * The class constructor
   */
  public LuceneIndexer() {

  }

  /**
   * Get a new IndexWriter initialized with the current index file.
   * 
   * @return IndexWriter the index writer
   * @throws IOException
   * @see IndexWriter
   */
  private IndexWriter getIndexWriter() throws IOException {

    Directory directory = new SimpleFSDirectory(INDEX_PATH);
    IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_46, analyzer);

    return new IndexWriter(directory, conf);
  }

  @Override
  public void createIndexForFile(FileEntity entity) throws IOException, TikaException, SAXException,
      NullPointerException {

    IndexWriter writer = getIndexWriter();
    BufferedInputStream bis = null;

    try {
      File file = new File(entity.getPath() + entity.getFileName());
      /*
       * Setup tika parser for autodetect und parse content and metadata
       */

      if (writer != null) {

        if (file != null && file.exists() && file.canRead()) {

          bis = new BufferedInputStream(new FileInputStream(file));
          Metadata metadata = new Metadata();
          BodyContentHandler ch = new BodyContentHandler(PARSE_SIZE);
          AutoDetectParser parser = new AutoDetectParser();
          MimeTypes mTypes = new MimeTypes();

          try {

            parser.parse(bis, ch, metadata, new ParseContext());
          } catch (SAXException | IOException | TikaException e) {

            e.printStackTrace();
            System.out.println("Probably the max parse size was reached.");
          }

          MediaType mediaType = mTypes.detect(bis, metadata);

          metadata.set(Metadata.CONTENT_TYPE, mediaType.getType());

          /*
           * create index document
           */
          Document document = new Document();

          document.add(new StringField(FILEID_FIELD, entity.getId().toString(), Store.YES));
          document.add(new TextField(FILENAME_FIELD, entity.getDisplayedFilename(), Store.YES));
          document.add(new StringField(FILENAME_FIELD, entity.getDisplayedFilename(), Store.YES));
          document.add(new TextField(DESCRIPTION_FIELD, entity.getDescription(), Store.YES));
          document
              .add(new StringField(CATEGORY_FIELD, Constants.contentIdDescMap.get(entity.getContentID()), Store.YES));
          for (CategoryEntity c : entity.getCategories()) {
            document.add(new StringField(CATEGORY_FIELD, c.getCategoryName(), Store.YES));
            document.add(new TextField(CATEGORY_FIELD, c.getCategoryName(), Store.YES));
          }

          for (String key : metadata.names()) {
            String[] values = metadata.getValues(key);
            for (String val : values) {
              document.add(new TextField(META_FIELD, val, Store.YES));
            }
          }

          /*
           * fill the document for different contentID 1 is music, 2 is picture, 3 is text see the constants.java in
           * SimilaritySearchEJBClient
           */

          if (mediaType.equals(MediaType.TEXT_HTML) || mediaType.equals(MediaType.TEXT_PLAIN)
              || mediaType.getType().equals("text")) {

            document.add(new TextField(CONTENT_FIELD, tika.parseToString(file), Store.YES));
          }

          writer.addDocument(document);

        } else {

          throw new IOException("Cannot access file.");
        }
      } else {

        throw new IOException("Could not get an IndexWriter.");
      }
    } finally {

      if (bis != null) {

        bis.close();
      }
      writer.close();
    }
  }

  @Override
  public void deleteIndexForFile(FileEntity entity) throws IOException {

    IndexWriter writer;
    writer = getIndexWriter();
    try {
      if (writer != null) {

        writer.deleteDocuments(new Term("fileID", entity.getId().toString()));
      } else {

        throw new IOException("Could not get IndexWriter.");
      }
    } finally {

      writer.close();
    }
  }

  @Override
  public List<BigInteger> searchInIndex(String srchStrng) throws IOException, ParseException {

    return searchInIndex(srchStrng, FIELDS);
  }

  @Override
  public List<BigInteger> searchInIndex(String srchStrng, String[] fields) throws IOException, ParseException,
      NullPointerException {

    List<BigInteger> fileIDs = new ArrayList<BigInteger>();
    Directory directory = SimpleFSDirectory.open(INDEX_PATH);

    if (directory != null) {
      IndexReader reader = DirectoryReader.open(directory);

      try {
        if (reader != null) {
          IndexSearcher searcher = new IndexSearcher(reader);

          String[] queries = new String[fields.length];
          for (int i = 0; i < queries.length; i++) {
            queries[i] = srchStrng;
          }

          Query query = MultiFieldQueryParser.parse(Version.LUCENE_46, queries, fields, analyzer);

          TopDocs tDocs = searcher.search(query, 10);

          if (tDocs != null) {

            for (ScoreDoc sd : tDocs.scoreDocs) {

              Document doc = reader.document(sd.doc);

              fileIDs.add(new BigInteger((doc.getField(FILEID_FIELD).stringValue())));
            }
          }
        }
      } finally {

        reader.close();
      }
    } else {
      throw new IOException("Could not open Indexing Directory.");
    }

    return fileIDs;
  }
}
